//=============================================================================
//
// Controls:
// - A, D, W, S: Move Camera
// - X, Z: Zoom in, zoom out 
//
//=============================================================================

#include "MainApplication.h"

#include "D3DColorUtils.h"
#include "D3DErrorChecker.h"
#include "MathUtils.h"

namespace 
{
	const unsigned __int32 kGridRows = 149;
	const unsigned __int32 kGridColumns = 149;
	const unsigned __int32 kWaveRows = 261;
	const unsigned __int32 kWaveColumns = 261;
}

namespace D3D10
{
	MainApplication::MainApplication(const HINSTANCE hInstance)
		: D3DFramework(hInstance)
		, mLand(kGridRows, kGridColumns)
		, mWaves(kWaveRows, kWaveColumns)
		, mEffect(0)
		, mTechnique(0)
		, mInputLayout(0)
		, mEffectWVPVariable(0)
		, mEffectWorldVariable(0)
		, mEffectTextureMatrixVariable(0)
		, mEffectEyePositionVariable(0)
		, mEffectLightVariable(0)
		, mEffectDiffuseMapVariable(0)
		, mEffectSpecularMapVariable(0)
		, mGrassMapRV(0)
		, mSpecularMapRV(0)
		, mBlendState(0)
		, mEyePosition(0.0f, 0.0f, 0.0f)
		, mWaterTextureOffset(0.0f, 0.0f)
		, mRadius(100.0f)
		, mTheta(0.0f)
		, mPhi(D3D10Utils::PI * 0.4f)
		, mWavesTranslationOffset(0.0f)
	{
		D3DXMatrixIdentity(&mLandWorld);
		D3DXMatrixIdentity(&mWavesWorld);
		D3DXMatrixIdentity(&mView);
		D3DXMatrixIdentity(&mProjection);
		D3DXMatrixIdentity(&mWVP); 

		D3DXMatrixTranslation(&mWavesWorld, 0.0f, 1.0f, 0.0f);

		//mClearColor = D3DXCOLOR(0.7f, 0.7f, 0.7f, 1.0f);
        mClearColor = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f);
	}
	
	MainApplication::~MainApplication()
	{
		if (mDevice)
			mDevice->ClearState();
	
		mEffect->Release();
		mInputLayout->Release();
	}
	
	void MainApplication::init()
	{
		D3DFramework::init();

		mLand.init(mDevice, 1.0f, 1.0f);
		mWaves.init(mDevice, 0.5f, 0.03f, 3.25f, 0.0f);
		initLights();

        HRESULT hr = D3DX10CreateShaderResourceViewFromFile(mDevice, L"chapter8_land/grass.dds", 0, 0, &mGrassMapRV, 0);
		D3D10Utils::ErrorChecker(hr);

        hr = D3DX10CreateShaderResourceViewFromFile(mDevice, L"chapter8_land/water.dds", 0, 0, &mWaterMapRV, 0);
        D3D10Utils::ErrorChecker(hr);

        hr = D3DX10CreateShaderResourceViewFromFile(mDevice, L"chapter8_land/defaultspec.dds", 0, 0, &mSpecularMapRV, 0);
        D3D10Utils::ErrorChecker(hr);
		
		initEffect();
		initInputLayout();
		initBlendState();
		
		// Generate some waves at start up.
		for(int k = 0; k < 150; ++k)
		{ 
			unsigned __int32 i = 5 + rand() % 250;
			unsigned __int32 j = 5 + rand() % 250;

			const float r = D3D10Utils::RandF(0.5f, 1.25f);

			mWaves.disturb(i, j, r);
		}	
	}
	
	void MainApplication::onResize()
	{
		D3DFramework::onResize();
	
		// Update projection perspective matrix.
		const float aspect = static_cast<float> (mClientWidth) / static_cast<float> (mClientHeight);
		D3DXMatrixPerspectiveFovLH(&mProjection, D3D10Utils::PI * 0.25f, aspect, 1.0f, 1000.0f);
	}
	
	void MainApplication::updateScene(const float deltaTime)
	{
		D3DFramework::updateScene(deltaTime);
	
		updateViewMatrix(deltaTime);

		updateWaves(deltaTime);

        // Update spot light cone angle
        if (GetAsyncKeyState('N') & 0x8000)
            mParallelLight.mSpotLightPower -= 0.25f;
        if (GetAsyncKeyState('M') & 0x8000)
            mParallelLight.mSpotLightPower += 0.25f;

        // The point light circles the scene as a function of time,
        // staying 7 units above the land's or water's surface.
        //mParallelLight.mPosition.x = 50.0f * cosf(mTimer.getInGameTime());
       // mParallelLight.mPosition.z = 50.0f * sinf(mTimer.getInGameTime());
        //mParallelLight.mPosition.y = D3D10Utils::Max(D3D10Utils::getHeight(mParallelLight.mPosition.x, mParallelLight.mPosition.z), 0.0f) + 7.0f;

        // The spotlight takes on the camera position and is aimed in the 
        // same direction the camera is looking. In this way, it looks 
        // like we are holding a flashlight.
        mParallelLight.mPosition = D3DXVECTOR3(0.0f, 50.0f, 0.0f);//mEyePosition;
        D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
        const D3DXVECTOR3 toTarget = target - mParallelLight.mPosition;
        D3DXVec3Normalize(&mParallelLight.mDirection, &toTarget);
	}
	
	void MainApplication::drawScene()
	{
		D3DFramework::drawScene();
	
        restoreDefaultStates();

        mDevice->IASetInputLayout(mInputLayout);
		mDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

		// Set per frame constants.
		mEffectEyePositionVariable->SetRawValue(&mEyePosition, 0, sizeof(D3DXVECTOR3));
		mEffectLightVariable->SetRawValue(&mParallelLight, 0, sizeof(D3D10Utils::Light));

		// Scale texture coordinates by 5 units to map [0,1]-->[0,5]
		// so that the texture repeats five times in each direction.
		D3DXMATRIX scalingMatrix;
		D3DXMatrixScaling(&scalingMatrix, 5.0f, 5.0f, 1.0f);
		D3DXMATRIX landTextureMatrix = scalingMatrix;

		// Scale and translate the texture.
		D3DXMATRIX translationMatrix;
		D3DXMatrixTranslation(&translationMatrix, mWaterTextureOffset.x, mWaterTextureOffset.y, 0.0f);
		D3DXMATRIX waterTextureMatrix = scalingMatrix * translationMatrix;
	
		D3D10_TECHNIQUE_DESC techDesc;
		mTechnique->GetDesc(&techDesc);

		ID3D10EffectPass* pass = mTechnique->GetPassByIndex(0);

		// Draw land
		mWVP = mLandWorld * mView * mProjection;
		mEffectWVPVariable->SetMatrix(reinterpret_cast<float*> (&mWVP));
		mEffectWorldVariable->SetMatrix(reinterpret_cast<float*> (&mLandWorld));
		mEffectDiffuseMapVariable->SetResource(mGrassMapRV);
		mEffectTextureMatrixVariable->SetMatrix(reinterpret_cast<float*> (&landTextureMatrix));
		mEffectSpecularMapVariable->SetResource(mSpecularMapRV);
		pass->Apply(0);
		mLand.draw();

		// Draw waves
		D3DXMatrixTranslation(&mWavesWorld, 0.0f, mWavesTranslationOffset, 0.0f);
		mWVP = mWavesWorld * mView * mProjection;
		mEffectWVPVariable->SetMatrix(reinterpret_cast<float*> (&mWVP));
		mEffectWorldVariable->SetMatrix(reinterpret_cast<float*> (&mWavesWorld));
		mEffectDiffuseMapVariable->SetResource(mWaterMapRV);
		mEffectTextureMatrixVariable->SetMatrix(reinterpret_cast<float*> (&waterTextureMatrix));
		mEffectSpecularMapVariable->SetResource(mSpecularMapRV);
		pass->Apply(0);

        const float blendFactors[] = {0.0f, 0.0f, 0.0f, 0.0f};
		mDevice->OMSetBlendState(mBlendState, blendFactors, 0xffffffff);
		mWaves.draw();
		
		mSwapChain->Present(0, 0);
	}

	void MainApplication::initEffect()
	{
		unsigned __int32 shaderFlags = D3D10_SHADER_ENABLE_STRICTNESS;
#if defined( DEBUG ) || defined( _DEBUG )
		shaderFlags |= D3D10_SHADER_DEBUG;
		shaderFlags |= D3D10_SHADER_SKIP_OPTIMIZATION;
#endif

		ID3D10Blob* compilationErrors = 0;
		HRESULT hr = 0;
		hr = D3DX10CreateEffectFromFile(L"chapter8_land/mainAlpha.fx", 0, 0, 
			"fx_4_0", shaderFlags, 0, mDevice, 0, 0, &mEffect, &compilationErrors, 0);
		if (FAILED(hr))
		{
			if (compilationErrors)
			{
				MessageBoxA(0, static_cast<char*> (compilationErrors->GetBufferPointer()), 0, 0);
				compilationErrors->Release();
			}
			DXTrace(__FILE__, (unsigned __int32)__LINE__, hr, L"D3DX10CreateEffectFromFile", true);
		} 

		// Get shader variables
		mTechnique = mEffect->GetTechniqueByName("BlendingAndFogTech");
		mEffectWVPVariable = mEffect->GetVariableByName("gWVP")->AsMatrix();
		mEffectWorldVariable = mEffect->GetVariableByName("gWorld")->AsMatrix();
		mEffectTextureMatrixVariable = mEffect->GetVariableByName("gTextureMatrix")->AsMatrix();
		mEffectEyePositionVariable = mEffect->GetVariableByName("gEyePositionsW");
		mEffectLightVariable = mEffect->GetVariableByName("gLight");
		mEffectDiffuseMapVariable = mEffect->GetVariableByName("gDiffuseMap")->AsShaderResource();
		mEffectSpecularMapVariable = mEffect->GetVariableByName("gSpecularMap")->AsShaderResource();
	}
	
	void MainApplication::initInputLayout()
	{
		D3D10_INPUT_ELEMENT_DESC vertexDesc[] =
		{
			{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D10_INPUT_PER_VERTEX_DATA, 0},
			{"NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0},
			{"TEXCOORD",  0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D10_INPUT_PER_VERTEX_DATA, 0},
		};
	
		D3D10_PASS_DESC PassDesc;
		mTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);
		HRESULT hr = mDevice->CreateInputLayout(vertexDesc, 3, PassDesc.pIAInputSignature, PassDesc.IAInputSignatureSize, &mInputLayout);

		D3D10Utils::ErrorChecker(hr);
	}

	void MainApplication::initBlendState()
	{
		D3D10_BLEND_DESC blendDesc = {0};
		blendDesc.AlphaToCoverageEnable = false;
		blendDesc.BlendEnable[0] = true;
		blendDesc.SrcBlend = D3D10_BLEND_SRC_ALPHA;
		blendDesc.DestBlend = D3D10_BLEND_INV_SRC_ALPHA;
		blendDesc.BlendOp = D3D10_BLEND_OP_ADD;
		blendDesc.SrcBlendAlpha = D3D10_BLEND_ONE;
		blendDesc.DestBlendAlpha = D3D10_BLEND_ZERO;
		blendDesc.BlendOpAlpha = D3D10_BLEND_OP_ADD;
		blendDesc.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL;

		HRESULT hr = mDevice->CreateBlendState(&blendDesc, &mBlendState);
		D3D10Utils::ErrorChecker(hr);
	}

	void MainApplication::initLights()
	{
		// Set parallel light
		//mParallelLight.mDirection = D3DXVECTOR3(0.57735f, -0.57735f, 0.57735f);
		//mParallelLight.mAmbientColor = D3DXCOLOR(0.4f, 0.4f, 0.4f, 1.0f);
		//mParallelLight.mDiffuseColor = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
		//mParallelLight.mSpecularColor = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);	

        mParallelLight.mAmbientColor = D3DXCOLOR(0.4f, 0.4f, 0.4f, 1.0f);
        mParallelLight.mDiffuseColor = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
        mParallelLight.mSpecularColor = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
        mParallelLight.mAttenuation.x = 0.0f; // a0 coef
        mParallelLight.mAttenuation.y = 0.1f; // a1 coef
        mParallelLight.mAttenuation.z = 0.0f; // a2 coef
        mParallelLight.mRange = 50.0f;

        mParallelLight.mAmbientColor = D3DXCOLOR(0.4f, 0.4f, 0.4f, 1.0f);
        mParallelLight.mDiffuseColor = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); // Black light
        mParallelLight.mSpecularColor = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); // Black light
        mParallelLight.mAttenuation.x = 1.0f; // a0 coef
        mParallelLight.mAttenuation.y = 0.0f; // a1 coef
        mParallelLight.mAttenuation.z = 0.0f; // a2 coef
        mParallelLight.mSpotLightPower = 64.0f;
        mParallelLight.mRange = 10000.0f;
	}

    void MainApplication::restoreDefaultStates()
    {
        // Restore rasterizer state.
        mDevice->RSSetState(0);

        // Restore depth stencil state
        mDevice->OMSetDepthStencilState(0, 0);

        // Restore blend state
        const float blendFactors[] = {0.0f, 0.0f, 0.0f, 0.0f};
        mDevice->OMSetBlendState(0, blendFactors, 0xffffffff);
    }

	void MainApplication::updateViewMatrix(const float deltaTime)
	{
		// Update angles based on input to orbit camera around scene.
		if (GetAsyncKeyState('A') & 0x8000)	
			mTheta -= 1.0f * deltaTime;
		if (GetAsyncKeyState('D') & 0x8000)	
			mTheta += 1.0f * deltaTime;
		if (GetAsyncKeyState('W') & 0x8000)	
			mPhi -= 2.0f * deltaTime;
		if (GetAsyncKeyState('S') & 0x8000)	
			mPhi += 2.0f * deltaTime;
		if (GetAsyncKeyState('Z') & 0x8000)	
			mRadius -= 25.0f * deltaTime;
		if (GetAsyncKeyState('X') & 0x8000)	
			mRadius += 25.0f * deltaTime;

		// Restrict the angle mPhi and radius mRadius.
		if (mPhi < 0.1f)	
			mPhi = 0.1f;
		if (mPhi > D3D10Utils::PI - 0.1f)	
			mPhi = D3D10Utils::PI - 0.1f;

		if (mRadius < 25.0f) 
			mRadius = 25.0f;

		// Convert Spherical to Cartesian coordinates: mPhi measured from +y
		// and mTheta measured counterclockwise from -z.
		mEyePosition.x =  mRadius * sinf(mPhi) * sinf(mTheta);
		mEyePosition.z = -mRadius * sinf(mPhi) * cosf(mTheta);
		mEyePosition.y =  mRadius * cosf(mPhi);

		// Build the view matrix.
		D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
		D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
		D3DXMatrixLookAtLH(&mView, &mEyePosition, &target, &up);
	}	

	void MainApplication::updateWaves(const float deltaTime)
	{
		// Animate water texture as a function of time in the update function.
		mWaterTextureOffset.y += 0.1f * deltaTime;
		mWaterTextureOffset.x = 0.25f * sinf(4.0f * mWaterTextureOffset.y);
				
		// Update waves height.
		if (GetAsyncKeyState('U') & 0x8000)	
			mWavesTranslationOffset += 10.0f * deltaTime;
		if (GetAsyncKeyState('I') & 0x8000)	
			mWavesTranslationOffset -= 10.0f * deltaTime;

		mWaves.update(deltaTime);
	}
}
